using System;
using System.Collections.Generic;
using SharpObjects.Configuration.ConfigurationProviders;
using SharpObjects.EventBus;
using SharpObjects.Interfaces;
using SharpObjects.Instantiation.Services;
using SharpObjects.Shared;
using SharpObjects.Shared.ApplicationEvents;

namespace SharpObjects.Logging.LogCategories
{
    public class LogCategory : ILogCategory, IXmlInitializable, IDisposable  
    {
        private IOriginFilter originFilter = null;
        private string eventBusId = null;
        private IEventBus eventBus = null;
        private List<ILogTarget> logTargets = new List<ILogTarget>();

        public LogCategory()
        {
        }


        #region ILogCategory Members

        public string Name { get; set; }
        public bool IsEnabled { get; set; }

        public void Write(Origin origin, string message, params object[] messageArgs)
        {
            if(this.IsEnabled)
            {
                if(this.originFilter.Pass(origin))
                {
                    foreach(ILogTarget logTarget in this.logTargets)
                    {
                        try
                        {
                            message = string.Format(message, messageArgs);
                            ILogEntry logEntry = new LogEntry(message, null, origin, this.Name, true);
                            logTarget.Write(logEntry);
                        }
                        catch (Exception exception)
                        {
                            Log.AddErrorMessage(exception);
                        }
                    }
                }
            }
        }

        public void Write(Origin origin, Exception ex, bool appendStackTrace = true)
        {
            if (this.IsEnabled)
            {
                if (this.originFilter.Pass(origin))
                {
                    foreach (ILogTarget logTarget in this.logTargets)
                    {
                        try
                        {
                            string stackTrace = null;

                            if(appendStackTrace)
                            {
                                stackTrace = ex.StackTrace;
                            }

                            LogInnerException(logTarget, origin, ex.InnerException, appendStackTrace);
                            ILogEntry logEntry = new LogEntry(ex.Message, stackTrace, origin, this.Name, true);
                            logTarget.Write(logEntry);
                        }
                        catch (Exception exception)
                        {
                            Log.AddErrorMessage(exception);
                        }
                    }
                }
            }
        }

        #endregion

        private void LogInnerException(ILogTarget logTarget, Origin origin, Exception ex, bool appendStackTrace = true)
        {
            if(ex != null)
            {
                string stackTrace = null;

                if(appendStackTrace)
                {
                    stackTrace = ex.StackTrace;
                }

                LogInnerException(logTarget, origin, ex.InnerException, appendStackTrace);
                ILogEntry logEntry = new LogEntry("INNER EXCEPTION: " + ex.Message, stackTrace, origin, this.Name, true);
                logTarget.Write(logEntry);
            }
        }

        #region IXmlInitializable Members

        public void Initialize(System.Xml.XmlElement initializationElement)
        {
            lock (this)
            {
                XmlConfigurationProvider init = new XmlConfigurationProvider(initializationElement);
                
                if(initializationElement.HasAttribute("name"))
                {
                    this.Name = init.GetString("@name");
                }
                else
                {
                    this.Name = init.GetString("@id");
                }

                this.originFilter = init.GetObject<IOriginFilter>("origin_filter", new OriginFilter());

                this.eventBusId = init.GetString("event_bus_id", init.GetString("@event_bus_id", null));

                if (this.eventBusId != null)
                {
                    this.eventBus = ServiceFactory.Instance.GetService<IEventBus>(this.eventBusId);
                    this.eventBus.ApplicationEvent += new ApplicationEventHandler(eventBus_ApplicationEvent);
                }

                this.IsEnabled = init.GetBoolean("is_enabled", init.GetBoolean("@is_enabled", true));

                this.logTargets.AddRange(init.GetObjects<ILogTarget>("log_target"));

                this.IsInitialized = true;
            }
        }

        void eventBus_ApplicationEvent(object applicationEvent)
        {
            if (this.IsEnabled)
            {
                if (applicationEvent is LogApplicationEvent)
                {
                    LogApplicationEvent logApplicationEvent = (LogApplicationEvent) applicationEvent;

                    if(logApplicationEvent.CategoryName == this.Name)
                    {
                        this.Write(logApplicationEvent.Origin, logApplicationEvent.Message);
                    }
                }
                else if (applicationEvent is LogExceptionApplicationEvent)
                {
                    LogExceptionApplicationEvent logExceptionApplicationEvent = (LogExceptionApplicationEvent) applicationEvent;

                    if(logExceptionApplicationEvent.CategoryName == this.Name)
                    {
                    this.Write(logExceptionApplicationEvent.Origin, logExceptionApplicationEvent.Exception, logExceptionApplicationEvent.AppendStackTrace);
                    }
                }
            }
        }

        private bool isInitialized = false;

        public bool IsInitialized
        {
            get
            {
                lock (this)
                {
                    return this.isInitialized;
                }
            }
            set
            {
                lock (this)
                {
                    this.isInitialized = value;
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if(this.isInitialized)
            {
                if(this.eventBus != null)
                {
                    this.eventBus.ApplicationEvent -= new ApplicationEventHandler(eventBus_ApplicationEvent);
                }

                foreach(ILogTarget logTarget in this.logTargets)
                {
                    logTarget.Release();
                }

                this.logTargets.Clear();

                this.isInitialized = false;
            }
        }

        #endregion
    }
}